package com.shop.shopmall.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.shop.shopmall.dto.LoginDto;
import com.shop.shopmall.pojo.Menu;
import com.shop.shopmall.pojo.Role;
import com.shop.shopmall.pojo.User;
import com.shop.shopmall.pojo.UserRole;
import com.shop.shopmall.service.UserService;
import com.shop.shopmall.mapper.UserMapper;
import com.shop.shopmall.utils.JwtUtil;
import com.shop.shopmall.vo.Result;
import jakarta.servlet.http.HttpServletRequest;
import org.apache.commons.lang3.StringUtils;
import org.apache.ibatis.annotations.Param;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;

/**
 * @author jamie
 * @description 针对表【user】的数据库操作Service实现
 * @createDate 2025-03-20 11:51:32
 */
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User>
        implements UserService, UserDetailsService {

    private static final Logger LOGGER = LoggerFactory.getLogger(UserServiceImpl.class);

    @Autowired
    @Lazy // 启用懒加载，解决security和service循环依赖问题
    private PasswordEncoder passwordEncoder;

    @Autowired
    private JwtUtil jwtUtil;

    @Autowired
    UserMapper userMapper;

    @Autowired
    RabbitTemplate rabbitTemplate;

    @Autowired
    StringRedisTemplate redisTemplate;

    @Override
    public User loadUserByUsername(String username) throws UsernameNotFoundException {
        // 根据用户名获取用户信息
        User user = userMapper.findUserByName(username);
        if (user == null) {
            return null;
        }
        // 获取用户对应的权限菜单，并设置到用户对象中
        user.setMenus(userMapper.findMenuByUserId(user.getId()));
        return user;
    }

    @Override
    public Result<?> login(LoginDto loginDto) {
        // 定义 token 字符串，用于存储登录成功后生成的 JWT 令牌
        String token = null;
        try {
            // 从 Redis 中获取预先存储的图形验证码
            String validCode = redisTemplate.opsForValue().get("validCode");

            // 判断客户端提交的验证码是否与 Redis 中的验证码一致
            if (!loginDto.getValidCode().equals(validCode)) {
                // 如果验证码不匹配，直接返回错误结果，提示验证码错误
                return Result.error("验证码错误");
            }

            // 根据传入的用户名加载对应的用户信息
            User user = loadUserByUsername(loginDto.getUsername());

            if( user == null ) {
                return Result.error("用户名填写错误");
            }

            // 检查用户状态，如果用户状态为 "0" 表示该用户已被禁用
            if ("0".equals(user.getStatus())) {
                // 返回错误信息，提示帐号已被禁用
                return Result.error("帐号已被禁用");
            }

            // 验证客户端传入的密码是否与数据库中存储的加密密码匹配
            if (!passwordEncoder.matches(loginDto.getPassword(), user.getPassword())) {
                // 获取 Redis 中记录的密码错误次数，如果不存在则视为 0
                int count = Integer.parseInt(
                        StringUtils.isBlank(
                                redisTemplate.opsForValue().get(loginDto.getUsername()))
                                ? String.valueOf(0)
                                : Objects.requireNonNull(redisTemplate.opsForValue().get(loginDto.getUsername()))
                ) + 1;

                // 更新 Redis 中存储的密码错误次数，将错误次数增加 1
                redisTemplate.opsForValue().set(loginDto.getUsername(), String.valueOf(count));

                // 当密码错误次数达到或超过 5 次时，锁定该用户账户
                if (count >= 5) {
                    // 构造锁定提示信息，包含用户名和锁定提示语
                    StringBuilder message = new StringBuilder();
                    message.append(loginDto.getUsername())
                            .append("，")
                            .append("您的用户已被锁定，请5分钟后重试");
                    // 通过消息队列（MQ）发送消息，将锁定信息通知相关系统或服务
                    rabbitTemplate.convertAndSend("shop-mall-exchange", "shop-mall-routing-key", message);
                }
                // 返回错误结果，提示用户名或密码错误
                return Result.error("用户名或密码错误");
            }

            // 如果密码验证成功，清除 Redis 中记录的密码错误次数，恢复初始状态
            redisTemplate.delete(loginDto.getUsername());

            // 根据用户名生成 JWT 令牌，用于后续认证和授权
            token = jwtUtil.createToken(user.getUsername());
        } catch (AuthenticationException e) {
            // 捕获认证过程中的异常，打印异常堆栈并记录警告日志
            e.printStackTrace();
            LOGGER.warn("登录异常:{}", e.getMessage());
        }
        // 返回成功结果，同时将生成的 token 作为响应数据返回给客户端
        return Result.success(token);
    }

    @Override
    public List<Menu> getMenuByUserId(int id) {
        return userMapper.getMenuByUserId(id);
    }

    @Override
    public IPage<User> getUsers(User user, Integer pageSize, Integer pageNum) {
        // 设置分页内容
        IPage<User> userIPage = new Page<>(pageNum, pageSize);
        LambdaQueryWrapper<User> lambdaQueryWrapper = new LambdaQueryWrapper<>();
        // 判断查询条件
        if (StringUtils.isNotBlank(user.getUsername())) {
            lambdaQueryWrapper.like(User::getUsername, user.getUsername());
        }
        if (StringUtils.isNotBlank(user.getPhone())) {
            lambdaQueryWrapper.like(User::getPhone, user.getPhone());
        }
        userIPage = userMapper.selectPage(userIPage, lambdaQueryWrapper);
        // 获取用户角色
        for (User record : userIPage.getRecords()) {
            // 根据用户ID获取用户角色集合
            List<Role> roleList = userMapper.getRoleByUserId(record.getId());
            Integer[] roles = roleList.stream().map(Role::getId).toArray(Integer[]::new);
            record.setRoles(roles);
            record.setRoleList(roleList);
        }
        return userIPage;
    }

    @Override
    public int updateStatus(int id, int status) {
        return userMapper.updateStatus(id, status);
    }

    @Override
    public int updatePass(int id, String password) {
        return userMapper.updatePass(id, password);
    }

}




